package com.ac.config;

import com.base.helper.Result;
import com.fasterxml.jackson.core.JsonParseException;
import com.ac.api.log.entity.AcLog;
import com.ac.api.log.service.AcLogService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.hibernate.validator.internal.engine.path.PathImpl;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.util.Date;
import java.util.List;
import java.util.Set;

/**
 * @author huyun
 * @ClassName GlobalExceptionHandler
 * @Description 全局异常处理
 * @Date 2019/8/7 15:11
 * @Version 1.0
 */
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {

    @Autowired
    private AcLogService systemLogService;

    /**
     * 业务异常处理
     *
     * @param e
     * @return
     */
    @ExceptionHandler(RuntimeException.class)
    public Result BusinessHandler(RuntimeException e) {
        log.error("接口访问失败", e);
        return Result.failed(e.getMessage());
    }


    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public Result badArgumentHandler(HttpRequestMethodNotSupportedException e) {
        log.error("接口访问失败", e);
        return Result.failed("请求方式不支持");
    }

    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public Result badArgumentHandler(HttpMediaTypeNotSupportedException e) {
        log.error("接口访问失败", e);
        return Result.failed("不支持的媒体类型");
    }

    @ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
    public Result badArgumentHandler(HttpMediaTypeNotAcceptableException e) {
        log.error("接口访问失败", e);
        return Result.failed("不可接受该请求");
    }

    @ExceptionHandler(MissingPathVariableException.class)
    public Result badArgumentHandler(MissingPathVariableException e) {
        StringBuilder builder = new StringBuilder("路径字段");
        builder.append(e.getVariableName());
        builder.append("校验不通过。描述: ");
        builder.append(e.getMessage());
        log.error("接口访问失败", e);
        return Result.failed(builder.toString());
    }

    @ExceptionHandler(MissingServletRequestParameterException.class)
    public Result badArgumentHandler(MissingServletRequestParameterException e) {
        StringBuilder builder = new StringBuilder("参数字段");
        builder.append(e.getParameterName());
        builder.append("校验不通过。描述：");
        builder.append(e.getMessage());
        log.error("参数校验失败", e);
        return Result.failed(builder.toString());
    }

    @ExceptionHandler(ServletRequestBindingException.class)
    public Result badArgumentHandler(ServletRequestBindingException e) {
        log.error("接口访问失败", e);
        return Result.failed("请求参数错误或不完整");
    }

    @ExceptionHandler(ConversionNotSupportedException.class)
    public Result badArgumentHandler(ConversionNotSupportedException e) {
        log.error("接口访问失败", e);
        return Result.failed("服务器正在升级，请耐心等待");
    }

    @ExceptionHandler(TypeMismatchException.class)
    public Result badArgumentHandler(TypeMismatchException e) {
        log.error(e.getMessage(), e);
        return Result.failed("请求参数错误或不完整");
    }

    @ExceptionHandler(HttpMessageNotReadableException.class)
    public Result badArgumentHandler(HttpMessageNotReadableException e) {
        log.error("接口访问失败", e);
        if (e.getCause() instanceof JsonParseException) {
            return Result.failed("JSON格式错误");
        } else {
            return Result.failed("请求参数错误或不完整");
        }
    }

    @ExceptionHandler(HttpMessageNotWritableException.class)
    public Result badArgumentHandler(HttpMessageNotWritableException e) {
        log.error("接口访问失败", e);
        return Result.failed("服务器正在升级，请耐心等待");
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result badArgumentHandler(MethodArgumentNotValidException e) {
        log.error(e.getMessage(), e);
        return Result.failed("请求参数错误或不完整:" + (buildMessages(e.getBindingResult())));
    }

    private String buildMessages(BindingResult result) {
        StringBuilder resultBuilder = new StringBuilder();

        List<ObjectError> errors = result.getAllErrors();
        if (errors != null && errors.size() > 0) {
            for (ObjectError error : errors) {
                if (error instanceof FieldError) {
                    FieldError fieldError  = (FieldError) error;
                    String     fieldName   = fieldError.getField();
                    String     fieldErrMsg = fieldError.getDefaultMessage();
                    resultBuilder.append(fieldName).append(" ").append(fieldErrMsg).append(";");
                }
            }
        }
        return resultBuilder.toString();
    }

    @ExceptionHandler(MissingServletRequestPartException.class)
    public Result badArgumentHandler(MissingServletRequestPartException e) {
        log.error(e.getMessage(), e);
        return Result.failed("请求参数错误或不完整");
    }

    @ExceptionHandler(BindException.class)
    public Result badArgumentHandler(BindException e) {
        log.error(e.getMessage(), e);
        return Result.failed("请求参数错误或不完整");
    }

    @ExceptionHandler(NoHandlerFoundException.class)
    public Result badArgumentHandler(NoHandlerFoundException e) {
        log.error(e.getMessage(), e);
        return Result.failed("未找到该路径");
    }

    @ExceptionHandler(AsyncRequestTimeoutException.class)
    public Result badArgumentHandler(AsyncRequestTimeoutException e) {
        log.error(e.getMessage(), e);
        return Result.failed("请求超时");
    }

    @ExceptionHandler(ValidationException.class)
    public Result badArgumentHandler(ValidationException e) {
        log.error(e.getMessage(), e);
        if (e instanceof ConstraintViolationException) {
            ConstraintViolationException exs        = (ConstraintViolationException) e;
            Set<ConstraintViolation<?>>  violations = exs.getConstraintViolations();
            for (ConstraintViolation<?> item : violations) {
                String message = ((PathImpl) item.getPropertyPath()).getLeafNode().getName() + item.getMessage();
                return Result.failed(message);
            }
        }
        return Result.failed("请求参数错误或不完整");
    }

    @ExceptionHandler(Exception.class)
    public Object seriousHandler(HttpServletRequest request, Exception e) {
        AcLog systemLog = new AcLog();
        systemLog.setCreatetime(new Date());
        systemLog.setError(ExceptionUtils.getStackTrace(e));
        systemLog.setDescription(e.toString());
        systemLogService.save(systemLog);
        log.error(e.getMessage(), e);
        return Result.failed("操作失败");
    }
}

